/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.unesp.ia.gridsolver.csp;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.LoggerFactory;
import solver.ResolutionPolicy;
import solver.Solver;
import solver.constraints.Constraint;
import solver.constraints.IntConstraintFactory;
import solver.variables.BoolVar;
import solver.variables.IntVar;
import solver.variables.VariableFactory;
import util.ESat;

/**
 *
 * @author Pardal
 */
public class GridScheduler extends AbstractProblem {

    private TSPData model;
    private IntVar[] horarioMaterias;
    private IntVar[] semestreMaterias;
    private BoolVar[] isDefault;
    private IntVar[] semestreWeight;
    private IntVar[] optionFitness;
    private IntVar finalFitness;
    private IntVar defaultCurriculumFitness;
    private IntVar nonDefaultSemestre;

    public GridScheduler() {
    }

    public GridScheduler(TSPData model) {
        this.model = model;
    }

    @Override
    public void createSolver() {
        solver = new Solver("MSP");
    }

    @Override
    public void buildModel() {

        horarioMaterias = VariableFactory.enumeratedArray("hMaterias", model.getSlots().size(), 0, model.getTimeDOmain(), solver);
        semestreMaterias = VariableFactory.enumeratedArray("sMaterias", model.getSlots().size(), 0, model.getTimeDOmain(), solver);
        isDefault = VariableFactory.boolArray("defaultSchedule", model.getSlots().size(), solver);

        semestreWeight = VariableFactory.enumeratedArray("sWeight", model.getSlots().size(), -9999, 9999, solver);
        optionFitness = VariableFactory.enumeratedArray("oFitness", 3, -9999, 9999, solver);
        finalFitness = VariableFactory.bounded("fFitness", -9999, 9999, solver);
        nonDefaultSemestre = VariableFactory.bounded("eSem", -9999, 9999, solver);
        defaultCurriculumFitness = VariableFactory.bounded("defCurriculum", -9999, 9999, solver);


        // Default curriculum
        //int[] defaultSchedule = new int[]{10, 0, 11, 5};

        // Restrict horarioMaterias domain 
        for (int i = 0; i < model.getSlots().size(); i++) {
            TSPMateria mData = model.getSlots().get(i);
            solver.post(IntConstraintFactory.member(horarioMaterias[i], mData.getHorariosOferta()));
            System.out.println("Posted: horarioMaterias[" + i + "] -> member " + Arrays.toString(mData.getHorariosOferta()));
        }

        // Restrict pre-requisites to different semesters, and order them.
        for (int i = 0; i < model.getSlots().size(); i++) {
            solver.post(IntConstraintFactory.eucl_div(horarioMaterias[i], VariableFactory.fixed(18, solver), semestreMaterias[i]));
            System.out.println("POsted: semestreMaterias[" + i + "] -> horarioMaterias[" + i + "] / 18");
        }
        for (int i = 0; i < model.getSlots().size(); i++) {
            TSPMateria mData = model.getSlots().get(i);
            for (int j = 0; j < mData.getPreRequisitos().length; j++) {
                int semPre = mData.getPreRequisitos()[j];
                solver.post(IntConstraintFactory.arithm(semestreMaterias[i], ">", semestreMaterias[semPre]));
                System.out.println("Posted: semestreMaterias[" + i + "] > semestreMaterias[" + semPre + "]");
            }
        }

        // Restricts all slots from a multiple-sloted subject to same semester.
        for (int i = 0; i < model.getSlots().size(); i++) {
            TSPMateria mData = model.getSlots().get(i);
            if ((mData.getSlotsMateria()[0] == i) && (mData.getSlotsMateria().length > 1)) {
                for (int j = 0; j < mData.getSlotsMateria().length; j++) {
                    if (i != mData.getSlotsMateria()[j]) {
                        int semSlot = mData.getSlotsMateria()[j];
                        solver.post(IntConstraintFactory.arithm(semestreMaterias[i], "=", semestreMaterias[semSlot]));
                        System.out.println("Posted: semestreMaterias[" + i + "] = semestreMaterias[" + semSlot + "]");
                    }
                }
            }
        }

        // Otpmizes to default curriculum (weight 2pt per subject)
        for (int i = 0; i < model.getSlots().size(); i++) {
            TSPMateria mData = model.getSlots().get(i);
            Constraint cons = IntConstraintFactory.arithm(horarioMaterias[i], "=", VariableFactory.fixed(mData.getHorarioDefault(), solver));
            solver.post(IntConstraintFactory.implies(isDefault[i], cons));
            System.out.println("Posted: horarioMaterias[" + i + "] -> default = " + mData.getHorarioDefault());
        }
        solver.post(IntConstraintFactory.sum(isDefault, defaultCurriculumFitness));
        solver.post(IntConstraintFactory.arithm(optionFitness[0], "=", VariableFactory.scale(defaultCurriculumFitness, 2)));

        // Optimizes to shortest completiion time (weight -4pt per subject)
        /*
        for (int i = 0; i < model.getSlots().size(); i++) {
            solver.post(IntConstraintFactory.arithm(semestreWeight[i], "=", VariableFactory.minus(VariableFactory.scale(semestreMaterias[i],1))));
        }
        solver.post(IntConstraintFactory.sum(semestreWeight, optionFitness[2]));
        */

        // Optimizes to shortest completition time (weight -10pt per subject)                                
        solver.post(IntConstraintFactory.among(nonDefaultSemestre, semestreMaterias, model.getOverDue()));
        solver.post(IntConstraintFactory.arithm(optionFitness[1], "=", VariableFactory.minus(VariableFactory.scale(nonDefaultSemestre, 10))));
        //solver.post(IntConstraintFactory.arithm(optionFitness[1], "=", VariableFactory.scale(nonDefaultSemestre, 10)));

        // Computes fitness to maximize
        solver.post(IntConstraintFactory.sum(optionFitness, finalFitness));

        // Avoid conflicts between subjects
        solver.post(IntConstraintFactory.alldifferent(horarioMaterias, "BC"));
    }

    @Override
    public void configureSearch() {
    }

    @Override
    public boolean optimalSolve() {
        solver.findOptimalSolution(ResolutionPolicy.MAXIMIZE, finalFitness);
        if (solver.isFeasible().equals(ESat.TRUE)) {
            return true;
        } else {
            return false;
        }

    }

    @Override
    public boolean nextSolution() {
        if (!solver.isCompleteSearch()) {
            solver.findSolution();
        }
        return solver.nextSolution();
    }

    public List<TSPSchedule> buildSchedule() {
        List<TSPSchedule> schedule = new ArrayList<TSPSchedule>();
        for (int i = 0; i < horarioMaterias.length; i++) {
            boolean contains = false;
            int id = model.getSlots().get(i).getIdMateria();
            for (TSPSchedule materia : schedule) {
                if (materia.getIdMateria() == id) {
                    contains = true;
                    materia.addHorario(horarioMaterias[i].getValue() + model.getOffset());
                }
            }
            if (!contains) {
                TSPSchedule materia = new TSPSchedule(model.getSlots().get(i).getIdMateria());
                materia.addHorario(horarioMaterias[i].getValue() + model.getOffset());
                schedule.add(materia);
            }
        }
        return schedule;
    }

    @Override
    public void prettyOut() {
        LoggerFactory.getLogger("bench").info("Timetable schduling problem");

        StringBuilder st = new StringBuilder();
        if (solver.isFeasible() != ESat.TRUE) {
            st.append("\tINFEASIBLE");
        } else {
            for (int i = 0; i < horarioMaterias.length; i++) {
                st.append("Subject ").append(i).append(" scheduled at time ").append(horarioMaterias[i].getValue());//.append("\n");
                st.append("\t(").append(semestreMaterias[i].getValue() + 1).append(")\n");
            }
            st.append("\nPeso curriculum: ").append(optionFitness[0].getValue()).append("\n");
            st.append("Peso tempo: ").append(optionFitness[1].getValue()).append("\n");
            st.append(solver.getMeasures().toOneLineString());
        }
        System.out.println(st.toString());
    }

    public void setModel(TSPData model) {
        this.model = model;
    }

    public TSPData getModel() {
        return model;
    }
}
